import {
  calcDistance,
  generateFlag,
  generateId,
  getPortsRotation,
  getStorageValue,
  getUrlParams,
  http,
  openWindow,
  showDialog,
  queryString,
  storage
} from "./utils";

class FlowDesigner {
  // 该流程的信息
  private flowProperties = <FlowInfo>{};
  // 记录分支出口的条件，key 元素id，value OutputCondition
  private outputConditions = new Map<string, OutputCondition>();
  // 每个节点设置的信息
  private nodeProperties = new Array<NodeProperty & Record<string, string>>();
  // 打开的窗口
  private win: Window | null = null;

  private readonly canvas: HTMLElement;
  private nodes: HTMLElement[] = [];
  private draggingNode: HTMLElement | null = null;
  // 按下鼠标时鼠标的位置
  private point = { x: 0, y: 0 };
  // 按下鼠标时元素的位置
  private offset = <Offset>{ left: 0, top: 0 };
  // 按下鼠标时点击port时line的位置
  private lineOffsets = new Map<HTMLElement, Offset>();
  // 处理拖动线的出入口时会单独用到，不用lineOffsets处理
  private jointedLine: HTMLElement | null = null;
  private hasStartNode = false;
  private hasEndNode = false;
  private readonly draggable: boolean = true;
  private readonly editable: boolean = true;
  // 所有port
  private followPorts = new Map<HTMLElement, Pos>();
  // 拖动port等待连接的点
  private jointingPorts = new Array<HTMLElement>();

  //对话框
  private readonly nodePropDialog: HTMLDialogElement

  constructor(editable: boolean = true, draggable?: boolean) {
    this.editable = editable;
    this.draggable = typeof (draggable) == "undefined" || draggable;
    this.canvas = document.querySelector('.canvas')!;
    this.nodePropDialog = document.querySelector<HTMLDialogElement>("#nodePropDialog")!;
    this.initToolbar();
    this.initCanvas();
  }

  private initToolbar() {
    const toolbar = document.querySelector('.toolbar')!;
    toolbar.addEventListener('click', (e) => {
      if (e.target instanceof HTMLButtonElement) {
        const btn = e.target as HTMLButtonElement;
        this.createNode(btn.dataset.type!);
      }
    });
  }
  private initCanvas() {
    // 关闭弹窗
    window.onbeforeunload = () => {
      if (this.win && !this.win.closed) {
        this.win.close();
      }
    }
    document.addEventListener('contextmenu', (event) => {
      // 阻止默认的上下文菜单显示
      event.preventDefault();
    });
    // 流程属性菜单
    this.makeDraggable(this.canvas);
    this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
    this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
    this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
  }

  private createNodeLabel(type: string) {
    const label = document.createElement('input');
    label.id = generateId('label');
    label.type = 'text';
    label.classList.add('node-label');
    label.value = type === 'start' ? '流程发起' :
      type === 'end' ? '流程结束' :
        type === 'branch' ? '分支' :
          type === 'merge' ? '聚合' :
            type === 'line' ? '连接线' : '处理节点';
    if (!this.editable) {
      label.disabled = true;
    }
    return label;
  }

  private createNode(type: string) {
    if (type === 'start' && this.hasStartNode) {
      alert('只能创建一个开始节点');
      return;
    }
    if (type === 'end' && this.hasEndNode) {
      alert('只能创建一个结束节点');
      return;
    }
    const node = document.createElement('div');
    node.id = generateId(type);
    node.dataset.flag = generateFlag(type);
    if (type == "line") {
      node.classList.add('line');
    } else {
      node.classList.add('node');
    }
    const label = this.createNodeLabel(type);
    label.dataset.flag = generateFlag(type);
    this.makeDraggable(label);
    switch (type) {
      case 'start':
        node.classList.add('start-node');
        node.innerText = "开始";
        this.hasStartNode = true;
        break;
      case 'end':
        node.classList.add('end-node');
        node.innerHTML = "结束";
        this.hasEndNode = true;
        break;
      case 'branch':
        node.classList.add('branch-node');
        node.innerHTML = "◆";
        break;
      case 'merge':
        node.classList.add('merge-node');
        node.innerHTML = "▶";
        break;
      case "normal":
        node.innerHTML = "节点";
        break;
      case "line":
        node.innerHTML = `<div class="arrow">▶</div>`
        break;
    }
    node.style.left = `20px`;
    node.style.top = `20px`;
    this.canvas.appendChild(node);
    this.nodes.push(node);
    node.appendChild(label);
    // 在节点创建后统一添加出入口
    this.addPorts(node, type);
    this.makeDraggable(node);
  }

  private addPort(node: HTMLElement, portType: string) {
    const port = document.createElement('div');
    port.classList.add('port', portType);
    port.id = generateId(portType);
    port.dataset.flag = generateFlag(portType);
    port.dataset.owner = port.id;
    if (node.classList.contains('branch-node')) {
      // 分支器出口添加在右下角
      port.style.right = '-6px';
      port.style.top = `${-6 + node.querySelectorAll('.out-port').length * 12}px`;
    } else if (node.classList.contains('merge-node')) {
      // 聚合器入口添加在顶部
      port.style.left = `${-6 + node.querySelectorAll('.in-port').length * 12}px`;
      port.style.top = '-6px';
    }
    node.appendChild(port);
    this.makeDraggable(port);
    this.addPortAndPos(port);
  }

  private addPorts(node: HTMLElement, type: string) {
    // 添加出入口,line的出入口在node外部
    if (type != 'start') {
      const inPort = document.createElement('div');
      inPort.id = generateId('inport');
      inPort.dataset.owner = node.id;
      inPort.classList.add('port', 'in-port');
      inPort.dataset.flag = generateFlag("in-port");
      if (type == "line") {
        this.canvas.appendChild(inPort);
        inPort.style.left = `${node.offsetLeft - 5}px`;
        inPort.style.top = `${node.offsetTop - node.offsetHeight}px`;
        inPort.style.zIndex = "10";
        this.makeDraggable(inPort);
      } else {
        this.canvas.appendChild(inPort);
        inPort.style.left = `${node.offsetLeft - 12}px`;
        inPort.style.top = `${node.offsetTop + node.offsetWidth / 2 - 5}px`;
        this.makeDraggable(inPort);
        this.addPortAndPos(inPort);
      }
    }
    if (type != 'end') {
      const outPort = document.createElement('div');
      outPort.id = generateId('outport');
      outPort.dataset.owner = node.id;
      outPort.classList.add('port', 'out-port');
      outPort.dataset.flag = generateFlag("out-port");
      if (type == "line") {
        this.canvas.appendChild(outPort);
        outPort.style.left = `${node.offsetLeft + node.offsetWidth + 6}px`;
        outPort.style.top = `${node.offsetTop + node.offsetHeight / 2 - 5}px`;
        outPort.style.zIndex = "10";
        this.makeDraggable(outPort);
      } else {
        this.canvas.appendChild(outPort);
        outPort.style.left = `${node.offsetLeft + node.offsetWidth + 3}px`;
        outPort.style.top = `${node.offsetTop + node.offsetWidth / 2 - 3}px`;
        this.makeDraggable(outPort);
        this.addPortAndPos(outPort);
      }
    }
    // 分支器和聚合器特殊处理
    if (type == 'branch') {
      // 添加右下角出口
      const outPort2 = document.createElement('div');
      outPort2.id = generateId('outport');
      outPort2.dataset.owner = node.id;
      outPort2.dataset.flag = generateFlag("out-port");
      outPort2.classList.add('port', 'out-port');
      this.canvas.appendChild(outPort2);
      outPort2.style.left = `${node.offsetLeft + node.offsetWidth - 6}px`;
      outPort2.style.top = `${node.offsetTop + node.offsetWidth / 2 + 24}px`;
      this.makeDraggable(outPort2);
      this.addPortAndPos(outPort2);
    }

    if (type == 'merge') {
      const inPort2 = document.createElement('div');
      inPort2.classList.add('port', 'in-port');
      inPort2.id = generateId('inport');
      inPort2.dataset.owner = node.id;
      inPort2.dataset.flag = generateFlag("in-port");
      this.canvas.appendChild(inPort2);
      inPort2.style.left = `${node.offsetLeft - 10}px`;
      inPort2.style.top = `${node.offsetTop + node.offsetWidth / 2 + 16}px`;
      this.makeDraggable(inPort2);
      this.addPortAndPos(inPort2);
    }
  }

  private showContextMenu(node: HTMLElement, x: number, y: number, flowProp: boolean = false) {
    // 是否可编辑状态
    if (!this.editable) {
      return;
    }

    // 节点名称输入框不弹
    if (node.tagName.toLowerCase() == "input") {
      return;
    }
    let branchOutport = null;
    // 出入口不弹出菜单,但分支器的出口、聚合器的入口可以弹出
    if (node.classList.contains("port")) {
      const port = node;
      const owner = this.canvas.querySelector<HTMLElement>("#" + port.dataset.owner)!;
      //是线上的出口
      if (owner.classList.contains("line") && port.classList.contains("out-port")) {
        return;
      }
      // 线上的入口特殊情况，则可弹出
      // 入口连接到了分支器的出口时，弹出的是分支器的出口
      if (owner.classList.contains("line") && port.classList.contains("in-port")) {
        // 找到连接到的分支出口
        const outport = this.canvas.querySelector<HTMLElement>("#" + port.dataset.jointed);
        if (outport && outport.dataset.owner) {
          const id = outport.dataset.owner;
          if (id && id.includes("branch")) {
            branchOutport = outport;
          }
        }
        // 如果不是线入口连接到了分支器的出口时
        if (!branchOutport) {
          return;
        }
      }

      //是开始、结束的出入口
      const el = this.canvas.querySelector("#" + port.dataset.owner);
      if (el && el.classList.contains("node")) {
        if (el.classList.contains("start-node") || el.classList.contains("end-node")) {
          return;
        }
        // 分支器的入口不弹，聚合器的出口不弹
        if (el.classList.contains("branch-node") && port.classList.contains("in-port")) {
          return;
        }
        if (el.classList.contains("merge-node") && port.classList.contains("out-port")) {
          return;
        }
      }
    }

    const existingMenu = document.querySelector('.node-context-menu');
    if (existingMenu) {
      existingMenu.remove();
    }

    const menu = document.createElement('div');
    menu.classList.add('node-context-menu');
    menu.style.position = 'fixed';
    menu.style.left = `${x}px`;
    menu.style.top = `${y}px`;

    // 流程属性，右键空白区域菜单
    if (flowProp) {
      const validateFlowItem = document.createElement('div');
      validateFlowItem.classList.add('menu-item');
      validateFlowItem.textContent = '验证设计';
      validateFlowItem.addEventListener('click', () => {
        if (this.validate()) {
          alert('设计验证通过');
        } else {
          alert('设计存在错误，请检查');
        }
        menu.remove();
      });

      const saveFlowItem = document.createElement('div');
      saveFlowItem.classList.add('menu-item');
      saveFlowItem.textContent = '保存设计';
      saveFlowItem.addEventListener('click', () => {
        this.save();
        alert('设计已保存');
        menu.remove();
      });

      const editFlowItem = document.createElement('div');
      editFlowItem.classList.add('menu-item');
      editFlowItem.textContent = '流程属性';
      editFlowItem.addEventListener('click', () => {
        // this.editFLowProperties(doc);
        menu.remove();
      });

      menu.appendChild(editFlowItem);
      menu.appendChild(validateFlowItem);
      menu.appendChild(saveFlowItem);

    } else {
      //节点属性
      const editItem = document.createElement('div');
      editItem.classList.add('menu-item');
      editItem.textContent = '属性编辑';
      editItem.addEventListener('click', () => {
        this.editNodeProperties((!!branchOutport) ? branchOutport : node);
        menu.remove();
      });
      if (!node.classList.contains("line")
        && !node.classList.contains("branch-node")
        && !node.classList.contains("merge-node")
        && !node.classList.contains("in-port")
        && !(node.dataset.owner?.startsWith("normal"))) {
        menu.appendChild(editItem);
      }

      // 分支器添加出口
      if (node.classList.contains("branch-node")) {
        const addOutPortItem = document.createElement('div');
        addOutPortItem.classList.add('menu-item');
        addOutPortItem.textContent = '添加出口';
        addOutPortItem.addEventListener('click', () => {
          this.addPort(node, 'out-port');
          menu.remove();
        });
        menu.appendChild(addOutPortItem);
      }
      // 聚合器添加入口
      if (node.classList.contains("merge-node")) {
        const addInPortItem = document.createElement('div');
        addInPortItem.classList.add('menu-item');
        addInPortItem.textContent = '添加入口';
        addInPortItem.addEventListener('click', () => {
          this.addPort(node, 'in-port');
          menu.remove();
        });
        menu.appendChild(addInPortItem);
      }

      const deleteItem = document.createElement('div');
      deleteItem.classList.add('menu-item');
      deleteItem.textContent = '删除';
      deleteItem.addEventListener('click', () => {
        this.deleteNode(node);
        menu.remove();
      });
      // 节点有关联时,普通节点的出口，取消删除操作
      if (!branchOutport && !node.dataset.owner?.startsWith("normal")) {
        menu.appendChild(deleteItem);
      }
    }
    if (menu.innerHTML != "") {
      document.body.appendChild(menu);
      document.addEventListener('click', () => menu.remove(), { once: true });
    }
  }

  private editNodeProperties(node: HTMLElement) {
    const flag = node.dataset.flag!;
    let conditon = getStorageValue(flag);
    
    let url = "";
    if (node.classList.contains("out-port")) {//分支的出口
      conditon = conditon || this.outputConditions.get(flag);
      url = "view/outport.html";
    } else if (node.classList.contains("node")
      || node.classList.contains("start-node")
      || node.classList.contains("end-node")) {
      if (!conditon) {
        conditon = this.nodeProperties.find(nodeProp => { nodeProp.nodeFlag == flag });
      }
      url = "view/node.html"
    }
    conditon = conditon ?? {};
    storage.setItem(flag, JSON.stringify(conditon));
    const qs = queryString({ flag });
    showDialog(url + "?" + qs, this.nodePropDialog);
  }

  private makeDraggable(node: HTMLElement) {
    node.addEventListener('mousedown', (evt) => {
      if (evt.target == node) {
        if (evt.button === 0 && this.draggable) {
          this.draggingNode = node;
          this.offset.left = node.offsetLeft;
          this.offset.top = node.offsetTop;
          this.point.x = evt.pageX;
          this.point.y = evt.pageY;
          this.canvas.querySelectorAll<HTMLElement>(".line").forEach(line => {
            this.lineOffsets.set(line, { top: line.offsetTop, left: line.offsetLeft })
          });
          //port时单独移动的,如果是拖拽的是line的port，有改变line效果，固还要记录line的位置
          if (node.classList.contains("port")) {
            const line = this.canvas.querySelector<HTMLElement>("#" + node.dataset.owner);
            if (line && line.classList.contains("line")) {
              this.jointedLine = line;
              // 线的出入口，与节点的出入口连接，同种类型的不能连接，线与线的出入口不能连接
              // 入口配出口，出口配入口
              const arr = Array.from(this.followPorts.keys())
                .filter(p => p.dataset.owner && !p.dataset.owner.startsWith("line"));
              if (node.classList.contains("in-port")) {
                this.jointingPorts = arr.filter(p => p.classList.contains("out-port"));
              } else {
                this.jointingPorts = arr.filter(p => p.classList.contains("in-port"));
              }
            }
          } else {
            // 设置关联拖动,重新记录各个port位置
            this.canvas.querySelectorAll<HTMLElement>(`[data-owner]`).forEach(p => {
              this.addPortAndPos(p);
            });
          }
        } else if (evt.button === 2) {
          evt.preventDefault();
          this.showContextMenu(node, evt.clientX, evt.clientY, node.classList.contains("canvas"));
        }
      }
    });
  }
  // 删除节点，包括线
  private deleteNode(node: HTMLElement) {
    this.nodes = this.nodes.filter(n => n !== node);
    if (node.classList.contains("start-node")) {
      this.hasStartNode = false;
    } else if (node.classList.contains("end-node")) {
      this.hasEndNode = false;
    }
    // 分支器的出口可删，但要保留2个
    // 聚合器的入口可删，但要保留2个
    if (node.classList.contains("port")) {
      const el = this.canvas.querySelector("#" + node.dataset.owner);
      if (el && el.classList.contains("node")) {
        if (el.classList.contains("branch-node")) {
          const len = el.querySelectorAll(".out-port").length;
          if (len <= 2) {
            alert("至少保留2个出口");
            return;
          }
        }
        if (el.classList.contains("merge-node")) {
          const len = el.querySelectorAll(".in-port").length;
          if (len <= 2) {
            alert("至少保留2个入口");
            return;
          }
        }
      }
    }
    // 删除关联的点，及出口规则
    this.canvas.querySelectorAll(`[data-owner="${node.id}"]`).forEach(el => {
      this.outputConditions.delete(el.id);
      el.remove();
    });

    node.remove();
  }
  private handleMouseDown(evt: MouseEvent) {
  }
  private handleMouseMove(evt: MouseEvent) {
    if (this.draggingNode) {
      if (this.draggingNode.classList.contains("canvas")) {
        return;
      }
      // 正常拖动效果
      const dx = evt.pageX - this.point.x;//x轴偏移量
      const dy = evt.pageY - this.point.y;//x轴偏移量
      let x = this.offset.left + dx;
      let y = this.offset.top + dy;
      this.draggingNode.style.left = `${x}px`;
      this.draggingNode.style.top = `${y}px`;
      if (this.draggingNode.classList.contains("port")) {
        // 画线
        this.drawLine(this.draggingNode, this.jointedLine!, dx, dy);
      } else {
        // 拖动其他元素时带出入口移动
        // 这些出入口也要带动关联的线一起移动
        this.followPorts.forEach((pos, port) => {
          if (port.dataset.owner == this.draggingNode!.id) {
            // 该节点的port
            port.style.left = `${pos.x + dx}px`;
            port.style.top = `${pos.y + dy}px`;
            // 找到关联的线的port
            const jp = this.getJointedPort(port);
            if (jp && this.followPorts.has(jp)) {
              const jpPos = this.followPorts.get(jp)!;
              jp.style.left = `${jpPos.x + dx}px`;
              jp.style.top = `${jpPos.y + dy}px`;
              const tempLine = document.querySelector<HTMLElement>("#" + jp.dataset.owner)!;
              this.drawLine(jp, tempLine, dx, dy);
            }
          }
        });
        // 拖动线时，造成连接脱落
        if (this.draggingNode.classList.contains("line")) {
          const d = dx * dx + dy * dy;
          if (d > 80) {
            this.canvas.querySelectorAll<HTMLElement>(`[data-owner='${this.draggingNode.id}']`).forEach(port => {
              const jointedId = port.dataset.jointed;
              port.style.backgroundColor = "#FFF";
              delete port.dataset.jointed;
              const tmp = document.querySelector<HTMLElement>("#" + jointedId);
              if (tmp) {
                tmp.style.backgroundColor = "red"
              }
            })
          }
        }
      }
    }
  }
  private handleMouseUp() {
    this.draggingNode = null;
    this.jointedLine = null;
  }
  private drawLine(port: HTMLElement, line: HTMLElement, dx: number, dy: number) {
    // 拖动分支的出口时
    if (!line) {
      return;
    }
    // 拖动线两端的进出口时，会改变线长度和旋转角度
    let anotherPort;
    document.querySelectorAll(`[data-owner='${line.id}']`).forEach(p => {
      if (p.id != port.id) {
        anotherPort = p;
        return false;
      }
    });
    // 计算线长度
    const x = port.offsetLeft - anotherPort!.offsetLeft;
    const y = port.offsetTop - anotherPort!.offsetTop;
    const len = Math.pow(x * x + y * y, 0.5);
    line.style.width = `${len - 6}px`;
    // 计算转动角度
    let angle = getPortsRotation(port, anotherPort!);
    // 拖动入口，line起点跟随
    if (port.id.startsWith("inport")) {
      const offset = this.lineOffsets.get(line)!;
      line.style.left = offset.left + dx + "px";
      line.style.top = offset.top + dy + "px";
    }
    line.style.transform = `rotate(${angle}deg)`;
    // 线的出入口连接处理
    this.jointPoint(port);
    line.querySelector("input")!.style.transform = `rotate(${360 - angle}deg) translateX(-50%)`;
  }
  //链接出入口,此拖动的port是线上的port
  private jointPoint(port: HTMLElement) {
    this.jointingPorts.forEach(p => {
      const len = calcDistance(port, p);
      if (len <= 8) {
        // p已被其他出入口关联，则拒绝关联
        if (this.canvas.querySelector(`[data-jointed='${p.id}']`)) {
          return;
        }
        port.style.backgroundColor = "green";
        // 关联port的id，jointed有值表示已关联
        port.dataset.jointed = p.id;
        if (this.jointedLine && port.classList.contains("in-port")) {
          this.jointedLine.style.left = `${port.offsetLeft + port.offsetWidth / 2}px`;
          this.jointedLine.style.top = `${port.offsetTop + 2}px`;
        }
        p.style.backgroundColor = "#fff";
      } else {
        // 移除关联port的id
        const id = port.dataset.jointed;
        if (id == p.id) {
          p.style.backgroundColor = "red";
          port.style.backgroundColor = "#fff";
          delete port.dataset.jointed;
        }
      }
    })
  }
  private addPortAndPos(el: Element) {
    const e = <HTMLElement>el;
    this.followPorts.set(e, { x: e.offsetLeft, y: e.offsetTop });
  }

  // 节点出入口关联的出入口（线上的出入口）
  private getJointedPort(port: HTMLElement): HTMLElement | null {
    return document.querySelector<HTMLElement>(`[data-jointed='${port.id}']`);
  }

  // 验证，是否符合规则，线路是否畅通
  private validate(): boolean {
    let len = document.querySelectorAll(".start-node").length;
    if (len == 0) {
      alert("没有开始节点")
      return false;
    }
    if (len != 1) {
      alert("只能有一个开始节点")
      return false;
    }
    len = document.querySelectorAll(".start-node").length;
    if (len == 0) {
      alert("没有结束节点");
      return false;
    }
    if (len != 1) {
      alert("只能有一个结束节点");
      return false;
    }
    // 节点是否都连接上
    // 查看非线的节点，是否都能找到对应的连接点
    // 没有data-jointed的是非线节点
    let r = this.followPorts.keys()
      .filter(port => !port.dataset.jointed)
      .every(port => !!document.querySelector(`[data-jointed="${port.id}"]`));
    if (!r) {
      alert("有节点未连接");
      return false;
    }
    //todo
    return true;
  }

  private save() {
    // 保存前先验证
    if (!this.validate()) {
      this.flowProperties.disabled = false;
      alert("验证未通过，此设计将设置为禁用");
    }
    this.flowProperties.content = this.canvas.innerHTML;

    // 找到设置过条件的出口,添加到集合中
    // 如果元素里面找不到，则该元素删除了，也删除对应的条件
    Array.from(this.outputConditions.keys())
      .filter(id => !this.canvas.querySelector("#" + id))
      .forEach(id => {
        this.outputConditions.delete(id);
      });
    this.canvas.querySelectorAll<HTMLElement>(`.out-port`).forEach(outport => {
      const flag = outport.dataset.flag;
      if (flag?.startsWith("out-port")) {
        const value = <OutputCondition>getStorageValue(flag);
        if (value) {
          value.nodeId = outport.id;
          this.outputConditions.set(value.nodeId, value);
        }
      }
    });

    const data = {
      flowProperties: this.flowProperties,
      outputConditions: this.outputConditions,
      nodeProperties: this.nodeProperties
    }
    const url = "flow-save?" + queryString(getUrlParams());
    http.post(url, data).then(res => {
      //todo
    })




  }

  // 远程加载恢复设计
  public load() {
    const params = getUrlParams();
    const qs = queryString({ tid: params["tid"] || "", fid: params["fid"] || "" });
    const url = "?" + qs;
    //  todo

    //  恢复 该流程的信息 flowProperties

    //  恢复 出口条件 outputConditions

    //  恢复 每个节点设置的信息 nodeProperties
  }
}

export default FlowDesigner;
